using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using log4net;
using converter.template;
using converter.template.item;

namespace converter
{
	public sealed class TagsMaker
	{
        // Traverse through template items and create Windward tags.
        // Return an updated template.
        public Template Process(Template template)
        {
            this.template = template;

            List<TemplateItem> list = template.Body; // list of body items
            TemplateItem item = template.Body.First(); // start item

            while (item != null)
            {
                Preorder(list, item);
                item = NextSibling(list, item);
            }

            return template;
        }

        // The list is a list of items which contains the current item.
        void Preorder(List<TemplateItem> list, TemplateItem item)
        {
            // Process the item.
            if (item is Table)
            {
                MarkForeachTag(list, item);
            }
            else if (item is TextRun)
            {
                // A TextRun might contain an expression which needs to be parsed.
                PlaceTag(list, (TextRun)item);
            }

            TemplateItem c = FirstChild(item);
            while (c != null)
            {
                Preorder(item.Items, c);
                c = NextSibling(item.Items, c);
            }
        }

        // Parse an expression and replace it with appropriate Windward tags.
        // textRun - a TextRun, may be representing an expression.
        // list - a list containing the item.
        private void PlaceTag(List<TemplateItem> list, TextRun textRun)
        {
            if (textRun.IsExpression())
            {
                // Parse the expression and replace it with an appropriate tag.
                Parser parse = new Parser(textRun.GetValue());
                string tag = string.Empty;
                try
                {
                    parse.Expr(template, tableStack);
                    tag = parse.Tag;
                }
                catch (Exception err)
                {
                    log.Debug(string.Format("Unparsed expression: {0}", textRun.GetValue()), err);
                }
                if (tag != string.Empty)
                    textRun.SetValue(tag);
            }
        }

        private sealed class Lexer
        {
            private readonly string expr;
            private readonly StringReader reader;
            private readonly StringBuilder buffer;

            public Lexer(string s)
            {
                this.expr = s;
                reader = new StringReader(s);
                buffer = new StringBuilder();
            }

            public string GetNextToken()
            {
                Scan();
                return buffer.ToString();
            }

            private void Scan()
            {
                buffer.Length = 0; // Clear the buffer.

                int c = reader.Read();

                if (IsEndOfStream(c))
                {
                    return;
                }
                else if (IsStartOfId(c))
                {
                    buffer.Append((char)c);
                    while (IsPartOfId(reader.Peek()))
                        buffer.Append((char)reader.Read());
                }
                else
                {
                    buffer.Append((char)c);
                }
            }

            private bool IsEndOfStream(int c)
            {
                return c == -1;
            }

            private bool IsStartOfId(int c)
            {
                return char.IsLetter((char)c);
            }

            private bool IsPartOfId(int c)
            {
                return char.IsLetterOrDigit((char)c) || (c == '_');
            }
        }

        private sealed class Parser
        {
            private readonly Lexer lex;
            private string tag = string.Empty;

            public Parser(string s)
            {
                lex = new Lexer(s);
            }

            public void Expr(Template template, Stack<Table> tableStack)
            {
                string tok;
                while ((tok = lex.GetNextToken()) != string.Empty)
                {
                    if (tok == "Tag")
                    {
                        Table table = tableStack.Peek();
                        DataSet ds = template.DataSets[table.DataSetName];

                        lex.GetNextToken(); // skip !
                        tok = lex.GetNextToken();
                        if (tok == "Foreach")
                        {
                            tag = string.Format("<wr:forEach select='{2}' datasource='{1}' var='{0}' >", table.ItemName, ds.GetDataSourceName(), ds.GetCommandText());
                        }
                        else
                        {
                            tag = "</wr:forEach>";
                            tableStack.Pop();
                        }
                    }
                    else if (tok == "Fields")
                    {
                        Table table = tableStack.Peek();
                        DataSet ds = template.DataSets[table.DataSetName];

                        lex.GetNextToken(); // skip ! or .
                        tok = lex.GetNextToken();
                        lex.GetNextToken(); // skip .
                        lex.GetNextToken(); // skip Value

                        DataSet.Field field = ds.GetField(tok);
                        if (field != null)
                            tag = string.Format("<wr:out select='${{{0}.{1}}}' datasource='{2}'/>", table.ItemName, field.Value, ds.GetDataSourceName());
                    }
                }
            }

            public string Tag
            {
                get
                {
                    return tag;
                }
            }
        }

        private void MarkForeachTag(List<TemplateItem> list, TemplateItem item)
        {
            Table table = item as Table;

            bool hasForEach = false;

            // find first detail row
            TableRow detailRow = null;
            foreach (TableRow row in item.Items)
                if (row.IsDetail)
                {
                    detailRow = row;
                    break;
                }
            if (detailRow != null)
            {
                // grab the first cell
                TableCell cell = FirstChild(detailRow) as TableCell;
                if (cell != null)
                {
                    // cell's paragraph
                    Paragraph para = FirstChild(cell) as Paragraph;
                    if (para != null)
                    {
                        // insert ForEach tag
                        TextRun tr = new TextRun("=Tag!Foreach");
                        para.Items.Insert(0, tr);
                        tableStack.Push(table);
                        hasForEach = true;
                    }
                }
            }
            if (hasForEach)
            {
                // put EndForEach after the table
                TextRun textRun = new TextRun("=Tag!EndForeach");
                Paragraph paragraph = new Paragraph();
                paragraph.Add(textRun);
                list.Insert(list.IndexOf(item) + 1, paragraph);
            }
        }

        bool HasChildren(TemplateItem item)
        {
            return (item.Items.Count > 0);
        }

        TemplateItem FirstChild(TemplateItem item)
        {
            return HasChildren(item) ? item.Items.First() : null;
        }

        TemplateItem NextSibling(List<TemplateItem> list, TemplateItem item)
        {
            // find an index of the item
            int idx = list.IndexOf(item);
            return list.ElementAtOrDefault(idx + 1); // next element or null
        }

        private static readonly ILog log = LogManager.GetLogger(typeof(TagsMaker));

        private Template template;
        private readonly Stack<Table> tableStack = new Stack<Table>();
	}
}